home *** CD-ROM | disk | FTP | other *** search
Text File | 1999-01-31 | 17.5 KB | 575 lines | [TEXT/ALFA] |
- ## -*-Tcl-*-
- # ###################################################################
- # Vince's Additions - an extension package for Alpha
- #
- # FILE: "completions.tcl"
- # created: 27/7/97 {12:43:41 am}
- # last update: 31/1/1999 {11:27:08 pm}
- # Author: Vince Darley
- # E-mail: <darley@fas.harvard.edu>
- # mail: Division of Engineering and Applied Sciences, Harvard University
- # Oxford Street, Cambridge MA 02138, USA
- # www: <http://www.fas.harvard.edu/~darley/>
- #
- # Copyright (c) 1997-1998 Vince Darley, all rights reserved
- #
- # Basic parts of the completion package -- to handle word and
- # file completion, but allowing very simple piggy-backing of
- # advanced completions.
- # ###################################################################
- ##
-
-
- namespace eval bind {}
- namespace eval completion {}
-
- # setup two globals
- ensureset completion::in_progress_proc error
- ensureset completion::in_progress_pos -1
-
- ##
- # -------------------------------------------------------------------------
- #
- # "bind::Completion" --
- #
- # If we're already completing, jump to that procedure, else go through
- # a mode-dependent list of completion procedures given by the array
- # 'completions', these return either '1' to indicate termination, or
- # '0' to say either that they failed or that they succeeded and that
- # further completion procedures may be applied.
- #
- # If no mode-dependent procedure list exists (as in a basic Alpha
- # installation), then just the 'user' completions and 'word'
- # completions are attempted.
- #
- # The list of procedures to try is copied into 'completion::chain',
- # so completion procs can modify that list if they like.
- # -------------------------------------------------------------------------
- ##
- proc bind::Completion {} {
- if {![completion::tabDeleteSelection]} return
-
- global completion::in_progress_proc
- if {[completion::notAlready]} {
- set completion::in_progress_proc error
- if {[completion::user]} return
- set m [modeALike]
- global completions mode completion::chain
- if {[info exists completions($mode)]} {
- set completion::chain $completions($mode)
- while 1 {
- if {[set c [lindex ${completion::chain} 0]] == ""} {
- break
- }
- set completion::chain [lreplace ${completion::chain} 0 0]
- if {[completion $m $c]} return
- }
- message "No further completions exist, perhaps you should write your own."
- } else {
- completion::word actual
- }
- }
- }
-
- proc completion::user {{cmd ""}} {
- return 0
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::fromList" --
- #
- # Given a 'cmd' prefix and the name of a list to search, that list
- # being stored in alphabetical order and starting/ending with
- # whitespace, this proc returns a list of all matches with 'cmd',
- # or "" if there were none. Updated so works with arrays too (Nov'96)
- #
- # It's quite an important procedure for completions, and must handle
- # pretty large lists, so it's worth optimising.
- #
- # Note '\\b' = word boundary, '\\s' = whitespace '\\S' = not-whitespace
- # -------------------------------------------------------------------------
- ##
- if {[info tclversion] < 8.0} {
- proc completion::fromList { __cmd slist } {
- global [lindex [split $slist "\("] 0]
- # Find all matches as a list --- a v. clever trick if I say so myself
- if {[regexp "(^|\\s)(${__cmd}\[^\\S\]*(\\s|\$))+" [set "$slist"] matches]} {
- return [string trim $matches]
- } else {
- return ""
- }
- }
- } else {
- proc completion::fromList { __cmd slist } {
- global [lindex [split $slist "\("] 0]
- regexp {^(.*)(.)$} $__cmd "" _find _last
- set _find "^[::quote::Regfind $_find]\[^$_last\].*"
- set first [lsearch -glob [set $slist] "${__cmd}*"]
- if {$first == -1} { return "" }
- set first [lrange [set $slist] $first end]
- set last [lsearch -regexp $first $_find]
- if {$last == -1} {
- incr last
- while {[string match "${__cmd}*" [lindex $first $last]]} {
- incr last
- }
- }
- return [lrange $first 0 [incr last -1]]
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::notAlready" --
- #
- # Call this to check if we should divert directly to a previously
- # registered completion procedure instead of starting from scratch.
- # -------------------------------------------------------------------------
- ##
- proc completion::notAlready {} {
- global completion::in_progress_proc completion::in_progress_pos
- # do the old completion if possible
- if {[pos::compare ${completion::in_progress_pos} == [getPos]] } {
- return [catch {completion [modeALike] ${completion::in_progress_proc}} ]
- } else {
- return 1
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::already" --
- #
- # If a completion routine has been called once, and would like to
- # be called again (to cycle through a number of possibilities), then
- # it should register itself with this procedure.
- # -------------------------------------------------------------------------
- ##
- proc completion::already { proc } {
- global completion::in_progress_proc completion::in_progress_pos
- # store the given completion
- set completion::in_progress_proc $proc
- set completion::in_progress_pos [getPos]
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "modeALike" --
- #
- # Some modes are really equivalent as far as commands etc. go, so
- # we don't bother with duplication.
- # -------------------------------------------------------------------------
- ##
- proc modeALike {} {
- global mode
- switch -- $mode {
- "C++" { return "C" }
- "Shel" { return "Tcl" }
- }
- return $mode
- }
-
-
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion" --
- #
- # Call a completion, by trying in order:
- # 1) error
- # 2) 'Type' is actually a generic completion routine
- # 3) '${mode}::Completion::${Type}' is a mode-specific routine
- # 4) 'completion::${type}' is a generic routine.
- #
- # We also check for expansion procedures of the forms:
- # 1) 'expansions::${type}'
- # 2) '${mode}::Expansion::${Type}', where Type begins with 'Ex'
- #
- # -------------------------------------------------------------------------
- ##
- proc completion { mode Type {match ""} } {
- if { $Type == "error" } { error "" }
- if {[string match "completion::*" $Type] \
- || [string match "expansions::*" $Type]} {
- return [$Type "${match}"]
- } elseif {[llength [info commands ${mode}::Completion::${Type}]]} {
- return [${mode}::Completion::${Type} "${match}"]
- } elseif {[llength [info commands ${mode}::Expansion::${Type}]]} {
- return [${mode}::Expansion::${Type} "${match}"]
- } else {
- return [eval completion::[string tolower $Type] \"${match}\"]
- }
- }
-
- proc completion::word {dummy} {
- return [completion::update completion::word]
- }
-
- proc completion::update { proc {got ""} {looking ""} } {
- if {[completion::general $got $looking]} {
- completion::already $proc
- return 1
- } else {
- completion::already error
- return 0
- }
- }
-
- proc completion::general { {got ""} {looking ""} } {
- global __wc__len __wc__prevPos completion::in_progress_pos \
- __wc__prevFound __wc__pat __wc__nextStart __wc__fwd \
- completion::in_progress_proc wordBreak \
- __wc_prevHits
-
- set pos [getPos]
- # Cursor changed place?
- if {[pos::compare $pos == ${completion::in_progress_pos}]} {
- # it is an old search
- set ret [completion::wc__newSearch $pos]
- if { $ret == 1 } {
- return 1
- } elseif { $ret == -1 } {
- select [pos::math $pos + [expr [string length $looking] - \
- [string length $__wc__prevFound] - [string length $got]]] $pos
- return 0
- }
- }
- # Start new search for completion::Word
- if { $got == "" } {
- # this is a normal completion
- set one [completion::lastWord start]
-
- set __wc__len [string length $one]
- set __wc__pat [quote::Regfind $one]
- append __wc__pat $wordBreak
- } else {
- # here we complete 'got' with something beginning 'looking'
- set start [pos::math $pos - [string length $got]]
- set one $looking
- set __wc__len [string length $one]
- set __wc__pat [quote::Regfind $one]
-
- # we want to find anything else which continues a 'word'
- append __wc__pat $wordBreak
- }
- set start [pos::math $start - 1]
- set __wc_prevHits {}
-
- if {![catch {search -s -f 0 -r 1 -i 0 -m 1 -- $__wc__pat $start} data]} {
- set d00 [lindex $data 0]
- set beg [pos::math $d00 + $__wc__len]
- set end [lindex $data 1]
- set __wc__prevFound [getText $d00 $end]
- lappend __wc_prevHits $__wc__prevFound
- set txt [getText $beg $end]
- goto $pos
- insertText $txt
- message "Found above."
- # Set a number of globals for possible next go-around
- set completion::in_progress_pos [getPos]
- set __wc__prevPos $pos
- set __wc__nextStart [pos::math $d00 - $__wc__len]
- set __wc__fwd 0
- return 1
- }
- if {![catch {search -s -f 1 -r 1 -i 0 -m 1 -- $__wc__pat $pos} data]} {
- set __wc__prevFound [getText [lindex $data 0] [lindex $data 1] ]
- lappend __wc_prevHits $__wc__prevFound
- set beg [pos::math [lindex $data 0] + $__wc__len]
- set end [lindex $data 1]
- set txt [getText $beg $end]
- goto $pos
- insertText $txt
- message "Found below."
- # Set a number of globals for possible next go-around
- set completion::in_progress_pos [getPos]
- set __wc__prevPos $pos
- set __wc__nextStart $end
- set __wc__fwd 1
- return 1
- }
- goto $pos
- return 0
- }
-
- # returns '1' if it succeeded
- # or -1 if failed completely
-
- proc completion::wc__newSearch { pos } {
- global __wc__len __wc__prevPos completion::in_progress_pos \
- __wc__prevFound __wc__pat __wc__nextStart __wc__fwd \
- __wc_prevHits
-
- while 1 {
- if {$__wc__fwd} {
- set fndMsg "Found below."
- } else {
- set fndMsg "Found above."
- }
- if {![catch {search -s -f $__wc__fwd -r 1 -i 0 -m 1 -- $__wc__pat $__wc__nextStart} data]} {
- set d00 [lindex $data 0]
- set beg [pos::math $d00 + $__wc__len]
- set end [lindex $data 1]
- set Hit [getText $d00 $end]
-
- #if (this Hit is not the same as the last one)
- if {[lsearch -exact $__wc_prevHits $Hit] == -1} {
-
- #add the hit to the list of previous hits
- lappend __wc_prevHits $Hit
- set __wc__prevFound $Hit
-
- set txt [getText $beg $end]
- deleteText $__wc__prevPos ${completion::in_progress_pos}
- goto $__wc__prevPos
- insertText $txt
- message $fndMsg
- # Set a number of globals for possible next go-around
- set completion::in_progress_pos [getPos]
- if {$__wc__fwd} {
- # Search Forwards
- set __wc__nextStart $end
- # End of found word
- } else {
- # Search Backwards
- set __wc__nextStart [pos::math $d00 - $__wc__len]
- # Before start of found word
- if {[pos::compare $__wc__nextStart <= [minPos]]} {
- set __wc__fwd 1
- set __wc__nextStart ${completion::in_progress_pos}
- }
- }
- return 1
- } else {
- # Move start of search after finding string again
- if {$__wc__fwd} {
- # Searching Forwards
- set __wc__nextStart $end
- # End of found word
- } else {
- # Still Searching Backwards
- set __wc__nextStart [pos::math $d00 - $__wc__len]
- # Before start of found word
- if {[pos::compare $__wc__nextStart <= [minPos]]} {
- set __wc__fwd 1
- set __wc__nextStart ${completion::in_progress_pos}
- }
- }
- }
- # End if hit is the same as a previous hit
- } else {
- # Search string not found
- if {$__wc__fwd} {
- # We were already looking forward, so the word is not in the file
- message "Not found."
- set completion::in_progress_pos -1
- goto $pos
- return -1
- } else {
- # start looking forward
- set __wc__fwd 1
- set __wc__nextStart ${completion::in_progress_pos}
- }
- }
-
- }
- return 0
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::lastWord" --
- #
- # Return the last word, without moving the cursor. If a variable name
- # is given, it is returned containing the position of the start of the
- # last word.
- #
- # Future extensions to this proc (in packages) may include further
- # optional arguments.
- # -------------------------------------------------------------------------
- ##
- proc completion::lastWord {{st ""}} {
- set pos [getPos]
- backwardWord
- if {$st != ""} {upvar $st beg}
- set beg [getPos]
- goto $pos
- if {[pos::compare $beg < [lineStart $pos]] \
- || [pos::compare $beg == $pos]} {error ""}
- return [getText $beg $pos]
- }
-
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::lastTwoWords" --
- #
- # Get last two words: returns the previous word, and sets the given var
- # to the word before that. Note that the 'word before that' actually
- # means all text from the start of that word up to the beginning of the
- # word which is returned. i.e. 'prev' will normally end in some sort of
- # space/punctuation.
- #
- # Future extensions to this proc (in packages) may include further
- # optional arguments.
- # -------------------------------------------------------------------------
- ##
- proc completion::lastTwoWords {prev} {
- set pos [getPos]
- backwardWord
- set beg_rhw [getPos]
- backwardWord
- set beg_lhw [getPos]
- goto $pos
- upvar $prev lhw
- if {[pos::compare $beg_lhw < [lineStart $pos]] \
- || [pos::compare $beg_lhw == $beg_rhw] } {
- set lhw { }
- } else {
- set lhw [getText $beg_lhw $beg_rhw]
- }
- return [getText $beg_rhw $pos]
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::tabDeleteSelection" --
- #
- # If there is a selection, this procedure is called by completion
- # routines to ask the user if it should be deleted (or if the
- # appropriate flag is set, to delete automatically).
- # -------------------------------------------------------------------------
- ##
- proc completion::tabDeleteSelection {} {
- global completion::in_progress_proc askDeleteSelection elecStopMarker
- if {([regexp "^\$|^$elecStopMarker" [getSelect]] || !$askDeleteSelection)} {
- deleteText [getPos] [selEnd]
- } else {
- if {[dialog::yesno "Delete selection?"]} {
- deleteText [getPos] [selEnd]
- set completion::in_progress_proc error
- } else {
- return 0
- }
- }
- return 1
- }
-
-
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::file" --
- #
- # Look back, see if there's a file/dir name and try and extend it.
- # Useful for Shel mode. This improves on the one that comes with
- # Alpha by default, and is much simpler.
- # -------------------------------------------------------------------------
- ##
- proc completion::filename { {dummy ""}} {
- set pos [getPos]
- set res [search -s -f 0 -i 0 -m 0 -r 1 -n -- "\[\"\{ \t\r\n\]" [pos::math $pos - 1]]
- if {[string length $res]} {
- set from [lindex $res 1]
- if {[pos::compare $from < $pos]} {
- set pre ":"
- set text [getText $from $pos]
- if {[catch {glob ":${text}*"} globbed]} {
- if {[catch {glob "${text}*"} globbed]} {
- return 0
- }
- set pre ""
- }
- completion::Find "$pre$text" $globbed
- return 1
- }
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "completion::Find" --
- #
- # Insert the completion of 'cmd' from the list 'matches', and return
- # the complete match if there was one.
- #
- # 'cmd' is what we have, 'matches' is a list of things which can complete
- # it, and 'forcequery' says don't bother with partial completions: if
- # we can't finish the command off, present the user with a list.
- # -------------------------------------------------------------------------
- ##
- proc completion::Find { cmd matches {isdbllist 0} {forcequery 0} {addQuery ""} {addAction ""}} {
- global listPickIfMultCmps __univ_NotBlocked listPickIfNonUniqueStuckCmp
-
- set cmdlen [string length $cmd]
- set mquery [set match [lindex $matches 0]]
- if {$isdbllist} { set match [lindex [lindex $match 0] 0]}
- if { [set cmdnum [llength $matches]] == 1 || $match == $cmd } {
- # It's unique or already a command, so insert it
- # and turn off cmd completion.
- if {$cmdnum != 1 && $listPickIfNonUniqueStuckCmp \
- && (![catch { set match [listpick -p "Pick a completion" -L $mquery $matches]}])} {
- if {$isdbllist} { set match [lindex [lindex $match 0] 0]}
- } else {
- message "Text is now a maximal completion."
- # so we move on
- }
- set maxcompletion [string range $match $cmdlen end]
- insertText $maxcompletion
- # so we move on
- return $match
- } else {
- set item [lindex $matches [incr cmdnum -1]]
- if {$isdbllist} { set item [lindex [lindex $item 0] 0] }
- set p [string length [largestPrefix [list $match $item]]]
- #set p $cmdlen
- #while {[string index $match $p]==[string index $item $p]} {incr p}
- if { $p == $cmdlen || $forcequery } {
- beep
- if {$listPickIfMultCmps || $forcequery} {
- if {$addQuery != ""} {
- lappend matches "————————————————————————" $addQuery
- }
- if {[catch { set match [listpick -p "Pick a completion" -L $mquery $matches]}] \
- || $match == "————————————————————————" } {
- message "Cancelled"
- return 1
- } else {
- if {$match == $addQuery} {
- $addAction
- return 1
- }
- if {$isdbllist} { set match [lindex [lindex $match 0] 0]}
- set maxcompletion [string range $match $cmdlen end]
- insertText $maxcompletion
- # so we move on
- return $match
- }
-
- } else {
- message "Can't extend --- ${matches}"
- set __univ_NotBlocked 0
- }
- } else {
- set maxcompletion [string range $match $cmdlen [incr p -1]]
- insertText $maxcompletion
- message "Matching: ${matches}"
- }
- return ""
- }
-
- }
-
-
-